local constant = {
    owner = {
        player       = sdk.find_type_definition("snow.hit.DamageFlowOwnerType"):get_field("Player"):get_data(),
        player_shell = sdk.find_type_definition("snow.hit.DamageFlowOwnerType"):get_field("PlayerShell"):get_data(),
        otomo        = sdk.find_type_definition("snow.hit.DamageFlowOwnerType"):get_field("Otomo"):get_data(),
        otomo_shell  = sdk.find_type_definition("snow.hit.DamageFlowOwnerType"):get_field("OtomoShell"):get_data(),
    },
    attacker = {
        invalid = sdk.find_type_definition("snow.enemy.EnemyDef.DamageAttackerType"):get_field("Invalid"):get_data(),
    },
    type = {
        shell = sdk.find_type_definition("snow.enemy.EnemyDef.DamageCalcType"):get_field("Shell"):get_data(),
    },
}

local symbols = {
    damage_parameter = sdk.find_type_definition("snow.hit.EnemyCalcDamageInfo.AfterCalcInfo_DamageSide"):get_field("_CalcParam"),
    is_marionette    = sdk.find_type_definition("snow.hit.EnemyCalcDamageInfo.AfterCalcInfo_DamageSide"):get_method("get_IsMarionetteStartDamageStock"),
    is_bind_wire     = sdk.find_type_definition("snow.hit.EnemyCalcDamageInfo.AfterCalcInfo_DamageSide"):get_method("get_IsBindWire"),
    is_sub_timer     = sdk.find_type_definition("snow.hit.EnemyCalcDamageInfo.AfterCalcInfo_DamageSide"):get_method("get_IsSubMarionetteStartWaitTimer"),
    is_mystery       = sdk.find_type_definition("snow.enemy.EnemyCharacterBase"):get_method("isEnableMystery"),
    is_online        = sdk.find_type_definition("snow.LobbyManager"):get_method("isOnline"),

    set_marionette = sdk.find_type_definition("snow.hit.EnemyCalcDamageInfo.AfterCalcInfo_DamageSide"):get_method("set_IsMarionetteStartDamageStock"),
    set_bind_wire  = sdk.find_type_definition("snow.hit.EnemyCalcDamageInfo.AfterCalcInfo_DamageSide"):get_method("set_IsBindWire"),
    set_bind_id    = sdk.find_type_definition("snow.hit.EnemyCalcDamageInfo.AfterCalcInfo_DamageSide"):get_method("set_BindWireConnectTargetId"),
    set_sub_timer  = sdk.find_type_definition("snow.hit.EnemyCalcDamageInfo.AfterCalcInfo_DamageSide"):get_method("set_IsSubMarionetteStartWaitTimer"),
    get_attacker   = sdk.find_type_definition("snow.hit.EnemyCalcDamageInfo.AfterCalcInfo_DamageSide"):get_method("get_DamageAttackerType"),
    get_type       = sdk.find_type_definition("snow.enemy.EnemyDamageCalcParam"):get_method("get_CalcType"),
    get_owner      = sdk.find_type_definition("snow.enemy.EnemyDamageCalcParam"):get_method("get_OwnerType"),

    get_mystery_parameter = sdk.find_type_definition("snow.enemy.EnemyCharacterBase"):get_method("get_MysteryParam"),
    set_mystery_damage    = sdk.find_type_definition("snow.enemy.EnemyMysteryParam"):get_method("set_MaximumActivityReleaseDamageStock"),
}

local settings = {
    no_pacify     = true,
    no_calm       = false,
    no_marionette = true,
    no_wire_bind  = true,
    fire_at_will  = false,
}

local lobby_manager = nil
local function is_online()
    if not lobby_manager then
        lobby_manager = sdk.get_managed_singleton("snow.LobbyManager")
    end
    return lobby_manager and symbols.is_online(lobby_manager) or false
end

local function load_settings()
    local user_settings = json.load_file("berserker.json") or settings

    for index, value in pairs(settings) do
        if nil == user_settings[index] then
            user_settings[index] = value
        end
    end

    for index, _ in pairs(user_settings) do
        if nil == settings[index] then
            user_settings[index] = nil
        end
    end
    return user_settings
end

local function ui_draw_managed_checkbox(label, attribute_name)
    local changed, value = imgui.checkbox(label, settings[attribute_name])
    if changed then
        settings[attribute_name] = value
        json.dump_file("berserker.json", settings)
    end
end

sdk.hook(sdk.find_type_definition("snow.enemy.EnemyCharacterBase"):get_method("endMysteryMaximumActivity"),
    function(args)
        if settings.no_pacify
            and not is_online()
            and 1 == sdk.to_int64(args[3])
            and 1 == sdk.to_int64(args[4])
        then
            args[4] = sdk.to_ptr(0)
        end
        return sdk.PreHookResult.CALL_ORIGINAL
    end
)

sdk.hook(sdk.find_type_definition("snow.enemy.EnemyCharacterBase"):get_method("updateMystery"),
    function(args)
        if not settings.no_calm or is_online() then
            return sdk.PreHookResult.CALL_ORIGINAL
        end

        local monster = sdk.to_managed_object(args[2])
        if not symbols.is_mystery(monster) then
            return sdk.PreHookResult.CALL_ORIGINAL
        end
        symbols.set_mystery_damage(symbols.get_mystery_parameter(monster), 0)
    end
)

sdk.hook(sdk.find_type_definition("snow.enemy.EnemyCharacterBase"):get_method("afterCalcDamage_DamageSide"),
    function(args)
        if not settings.no_marionette
            and not settings.no_wire_bind
            and not settings.fire_at_will
            or is_online()
        then
           return sdk.PreHookResult.CALL_ORIGINAL
        end

        local damage    = sdk.to_managed_object(args[3])
        local parameter = symbols.damage_parameter:get_data(damage)
        local owner     = symbols.get_owner(parameter)

        if settings.no_marionette and symbols.is_marionette(damage)
            and (constant.owner.player == owner
                or constant.owner.player_shell == owner
                    and (symbols.get_type(parameter) ~= constant.type.shell
                        or symbols.get_attacker(damage) ~= constant.attacker.invalid)
                or constant.owner.otomo == owner
                or constant.owner.otomo_shell == owner)
        then
            symbols.set_marionette(damage, false)
        end

        if settings.no_wire_bind
            and (constant.owner.otomo == owner or constant.owner.otomo_shell == owner)
            and symbols.is_bind_wire(damage)
        then
            symbols.set_bind_wire(damage, false)
            symbols.set_bind_id(damage, 0)
        end

        if settings.fire_at_will and symbols.is_sub_timer(damage) then
            symbols.set_sub_timer(damage, false)
        end

        return sdk.PreHookResult.CALL_ORIGINAL
    end
)

settings = load_settings()

re.on_config_save(function()
    json.dump_file("berserker.json", settings)
end)

re.on_draw_ui(function ()
    if imgui.tree_node("Berserker Mod") then
        ui_draw_managed_checkbox("Won't be pacified", "no_pacify")
        ui_draw_managed_checkbox("Won't be calmed", "no_calm")
        ui_draw_managed_checkbox("Disable wyvern riding", "no_marionette")
        ui_draw_managed_checkbox("Disable wire binding", "no_wire_bind")
        ui_draw_managed_checkbox("Ignore damage", "fire_at_will")
        imgui.tree_pop()
    end
end)